JavaScript之作用域(Scope),作用域链(Scope Chain)

官方概念

作用域(Scope):

javaScript中没有块级作用域、取而代之的使用了函数作用域、即变量在声明它们的函数以及这个函数体嵌套的任意函数体内都是有定义的 –《javaScript权威指南》

作用域链(Scope Chain):

当函数创建时会创建一个包含其父函数变量、父函数的父函数的变量对象、直至全局变量对象的一个作用域链,这个作用域被保存在函数内部的[[scope]]属性中,由于函数本身即是对象,可以理解[[scope]]是后台可以访问的一个属性,不可再JavaScript代码中访问,当函数调用时,会创建一个自己的活动对象、作为变量对象,被推入到执行环境作用域链的最前端,此时这个[[scope]]属性相当于一个变量对象的集合,并有访问的优先级。作用域链并不保存实际的变量对象,它是一个指针,指向内存中的变量对象列表。

执行环境(执行上下文Execution Contexts):

执行环境定义了变量或函数有权访问的其他数据、决定了它们各自的行为。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境 –《javaScript高级教程》

个人理解

作用域:

所谓作用域,就是变量或者是函数能作用的范围。

分为全局作用域和局部作用域:

1、全局作用域

除了函数中定义的变量之外,都是全局作用域。

举个栗子:

1
2
3
4
5
var a = 10;
function bar(){
console.log(a);
}
bar();//10

以上的a就是全局变量,到处可以访问a。
但是要理解变量声明提升慨念,以下代码涉及变量声明提升问题(博客也有自己写的一篇关于声明提升的文章):

1
2
3
4
5
6
var a = 10;
function bar(){
console.log(a);
var a = 20;
}
bar();//undefined

是的,你没看错。函数内的变量a由于被预解析(变量提升),变量a提升到bar函数内的顶部形成一个局部作用域,赋值位置不变,所以结果就是undefined。
上面代码实质执行是:

1
2
3
4
5
6
7
var a = 10;
function bar(){
var a
console.log(a); // undefined
a = 20;
}
bar(); // undefined

2、局部作用域

函数里用var声明的变量。

举个栗子:

1
2
3
4
5
6
var a = 10;
function bar(){
var a = 20;
console.log(a);
}
bar();//20

作用域链

一个变量随着父级函数一级一级往上搜索的查找,找到这个变量最近定义var的地方,直到找到为止,找不到就报错,这个程就是作用域链起的作用。

代码分析:

1
2
3
4
5
6
7
8
9
10
var a = 10;
function foo() { //外部函数
var b = 20;
function bar() { //内部函数
alert(a + b); //变量 a,b 一层一层往上找 这个过程就是作用域链的过程
}
return bar;
}

foo()(); // 30

没有块级作用域(至ES5),ES6中有块级作用域

ES6之前,除了函数之外的代码块都不具备块级作用域。

常见的经典例子:

1
2
3
4
5
6
for(var i=0;i<6;i++){
setTimeout(function(){
console.log(i);
},200);
}
//6 6 6 6 6 6

解决办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//通过立即执行函数(IIFE)解决
for(var i=0;i<6;i++){
(function(j){
setTimeout(function(){
console.log(j);
},200);
})(i)
}
//0 1 2 3 4 5

//通过ES6 let块作用域把var 换成 let 声明变量
for(let i=0;i<6;i++){
setTimeout(function(){
console.log(i);
},200);
}
//0 1 2 3 4 5

Buy me a cup of coffee,thanks!